package com.hongshu.web.service.recommendation;

import com.hongshu.web.service.recommendation.model.RecommendContext;
import com.hongshu.web.service.recommendation.model.RecallResult;
import com.hongshu.web.service.recommendation.model.UserProfile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * 多路召回服务
 * 实现协同过滤、内容召回、社交召回等多种召回策略
 *
 * @author hongshu
 * @date 2024/01/01
 */
@Service
@Slf4j
public class RecallService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private CollaborativeFilteringRecall collaborativeFilteringRecall;
    @Autowired
    private ContentBasedRecall contentBasedRecall;
    @Autowired
    private SocialRecall socialRecall;
    @Autowired
    private TrendingRecall trendingRecall;
    @Autowired
    private LocationRecall locationRecall;
    @Autowired
    private TagBasedRecall tagBasedRecall;
    private final ExecutorService executorService = Executors.newFixedThreadPool(6);


    /**
     * 多路召回主入口
     */
    public List<String> multiRecall(UserProfile userProfile, RecommendContext context) {
        try {
            log.info("开始多路召回，用户ID: {}", userProfile.getUserId());

            // 并行执行多路召回
            List<CompletableFuture<RecallResult>> futures = Arrays.asList(
                CompletableFuture.supplyAsync(() ->
                    collaborativeFilteringRecall.recall(userProfile, context), executorService),
                CompletableFuture.supplyAsync(() ->
                    contentBasedRecall.recall(userProfile, context), executorService),
                CompletableFuture.supplyAsync(() ->
                    socialRecall.recall(userProfile, context), executorService),
                CompletableFuture.supplyAsync(() ->
                    trendingRecall.recall(userProfile, context), executorService),
                CompletableFuture.supplyAsync(() ->
                    locationRecall.recall(userProfile, context), executorService),
                CompletableFuture.supplyAsync(() ->
                    tagBasedRecall.recall(userProfile, context), executorService)
            );
            // 等待所有召回完成
            CompletableFuture<Void> allFutures = CompletableFuture.allOf(
                futures.toArray(new CompletableFuture[0])
            );
            allFutures.get(); // 等待完成
            // 合并召回结果
            List<RecallResult> recallResults = futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());
            // 去重并合并
            Set<String> candidateItems = new HashSet<>();
            Map<String, Double> itemScores = new HashMap<>();

            for (RecallResult result : recallResults) {
                for (Map.Entry<String, Double> entry : result.getItemScores().entrySet()) {
                    String itemId = entry.getKey();
                    Double score = entry.getValue();

                    if (itemScores.containsKey(itemId)) {
                        // 取最高分
                        itemScores.put(itemId, Math.max(itemScores.get(itemId), score));
                    } else {
                        itemScores.put(itemId, score);
                    }
                    candidateItems.add(itemId);
                }
            }
            log.info("多路召回完成，候选物品总数: {}", candidateItems.size());
            // 按分数排序并返回
            return itemScores.entrySet().stream()
                .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("多路召回异常", e);
            // 降级到热门召回
            return new ArrayList<>(trendingRecall.recall(userProfile, context).getItemIds());
        }
    }

    /**
     * 协同过滤召回
     */
    @Service
    public static class CollaborativeFilteringRecall {

        @Autowired
        private RedisTemplate<String, Object> redisTemplate;

        public RecallResult recall(UserProfile userProfile, RecommendContext context) {
            try {
                log.info("开始协同过滤召回");

                // 1. 获取相似用户
                List<String> similarUsers = getSimilarUsers(userProfile.getUserId());

                // 2. 获取相似用户喜欢的物品
                Set<String> candidateItems = new HashSet<>();
                Map<String, Double> itemScores = new HashMap<>();

                for (String similarUser : similarUsers) {
                    List<String> likedItems = getUserLikedItems(similarUser);
                    double similarity = getUserSimilarity(userProfile.getUserId(), similarUser);

                    for (String itemId : likedItems) {
                        if (!userProfile.getLikedItems().contains(itemId)) {
                            candidateItems.add(itemId);
                            itemScores.merge(itemId, similarity, Double::sum);
                        }
                    }
                }

                log.info("协同过滤召回完成，候选物品数量: {}", candidateItems.size());
                return RecallResult.builder()
                        .candidateItems(candidateItems)
                        .itemScores(itemScores)
                        .build();

            } catch (Exception e) {
                log.error("协同过滤召回异常", e);
                return new RecallResult(new HashSet<>(), new HashMap<>());
            }
        }

        private List<String> getSimilarUsers(String userId) {
            // 从Redis获取相似用户列表
            String key = "similar_users:" + userId;
            @SuppressWarnings("unchecked")
            List<String> similarUsers = (List<String>) redisTemplate.opsForValue().get(key);
            return similarUsers != null ? similarUsers : new ArrayList<>();
        }

        private List<String> getUserLikedItems(String userId) {
            // 从Redis获取用户喜欢的物品
            String key = "user_liked_items:" + userId;
            @SuppressWarnings("unchecked")
            List<String> likedItems = (List<String>) redisTemplate.opsForValue().get(key);
            return likedItems != null ? likedItems : new ArrayList<>();
        }

        private double getUserSimilarity(String userId1, String userId2) {
            // 计算用户相似度（余弦相似度）
            String key = "user_similarity:" + userId1 + ":" + userId2;
            Double similarity = (Double) redisTemplate.opsForValue().get(key);
            return similarity != null ? similarity : 0.0;
        }
    }

    /**
     * 基于内容的召回
     */
    @Service
    public static class ContentBasedRecall {

        @Autowired
        private RedisTemplate<String, Object> redisTemplate;

        public RecallResult recall(UserProfile userProfile, RecommendContext context) {
            try {
                log.info("开始基于内容的召回");

                Set<String> candidateItems = new HashSet<>();
                Map<String, Double> itemScores = new HashMap<>();

                // 1. 获取用户兴趣标签
                List<String> userInterests = userProfile.getInterestTags();

                // 2. 基于标签召回
                for (String tag : userInterests) {
                    List<String> tagItems = getItemsByTag(tag);
                    for (String itemId : tagItems) {
                        if (!userProfile.getLikedItems().contains(itemId)) {
                            candidateItems.add(itemId);
                            itemScores.merge(itemId, 1.0, Double::sum);
                        }
                    }
                }

                log.info("基于内容的召回完成，候选物品数量: {}", candidateItems.size());
                return RecallResult.builder()
                        .candidateItems(candidateItems)
                        .itemScores(itemScores)
                        .build();

            } catch (Exception e) {
                log.error("基于内容的召回异常", e);
                return new RecallResult(new HashSet<>(), new HashMap<>());
            }
        }

        private List<String> getItemsByTag(String tag) {
            String key = "tag_items:" + tag;
            @SuppressWarnings("unchecked")
            List<String> items = (List<String>) redisTemplate.opsForValue().get(key);
            return items != null ? items : new ArrayList<>();
        }
    }

    /**
     * 社交召回
     */
    @Service
    public static class SocialRecall {

        @Autowired
        private RedisTemplate<String, Object> redisTemplate;

        public RecallResult recall(UserProfile userProfile, RecommendContext context) {
            try {
                log.info("开始社交召回");

                Set<String> candidateItems = new HashSet<>();
                Map<String, Double> itemScores = new HashMap<>();

                // 1. 获取用户关注的人
                List<String> followingUsers = userProfile.getFollowingUsers();

                // 2. 获取关注用户的内容
                for (String followingUser : followingUsers) {
                    List<String> userItems = getUserItems(followingUser);
                    for (String itemId : userItems) {
                        if (!userProfile.getLikedItems().contains(itemId)) {
                            candidateItems.add(itemId);
                            itemScores.merge(itemId, 1.0, Double::sum);
                        }
                    }
                }

                log.info("社交召回完成，候选物品数量: {}", candidateItems.size());
                return RecallResult.builder()
                        .candidateItems(candidateItems)
                        .itemScores(itemScores)
                        .build();

            } catch (Exception e) {
                log.error("社交召回异常", e);
                return new RecallResult(new HashSet<>(), new HashMap<>());
            }
        }

        private List<String> getUserItems(String userId) {
            String key = "user_items:" + userId;
            @SuppressWarnings("unchecked")
            List<String> items = (List<String>) redisTemplate.opsForValue().get(key);
            return items != null ? items : new ArrayList<>();
        }
    }

    /**
     * 热门召回
     */
    @Service
    public static class TrendingRecall {

        @Autowired
        private RedisTemplate<String, Object> redisTemplate;

        public RecallResult recall(UserProfile userProfile, RecommendContext context) {
            try {
                log.info("开始热门召回");

                // 从Redis获取热门物品
                String key = "trending_items";
                @SuppressWarnings("unchecked")
                List<String> trendingItems = (List<String>) redisTemplate.opsForValue().get(key);

                if (trendingItems == null) {
                    trendingItems = new ArrayList<>();
                }

                // 过滤用户已喜欢的物品
                Set<String> candidateItems = trendingItems.stream()
                    .filter(item -> !userProfile.getLikedItems().contains(item))
                    .collect(Collectors.toSet());

                Map<String, Double> itemScores = new HashMap<>();
                for (String itemId : candidateItems) {
                    itemScores.put(itemId, 1.0);
                }

                log.info("热门召回完成，候选物品数量: {}", candidateItems.size());
                return RecallResult.builder()
                        .candidateItems(candidateItems)
                        .itemScores(itemScores)
                        .build();

            } catch (Exception e) {
                log.error("热门召回异常", e);
                return new RecallResult(new HashSet<>(), new HashMap<>());
            }
        }
    }

    /**
     * 地理位置召回
     */
    @Service
    public static class LocationRecall {

        @Autowired
        private RedisTemplate<String, Object> redisTemplate;

        public RecallResult recall(UserProfile userProfile, RecommendContext context) {
            try {
                log.info("开始地理位置召回");

                Set<String> candidateItems = new HashSet<>();
                Map<String, Double> itemScores = new HashMap<>();

                // 1. 获取用户位置
                String userLocation = userProfile.getLocation();
                if (userLocation == null) {
                    return RecallResult.builder()
                        .candidateItems(candidateItems)
                        .itemScores(itemScores)
                        .build();
                }

                // 2. 获取附近的内容
                List<String> nearbyItems = getNearbyItems(userLocation);
                for (String itemId : nearbyItems) {
                    if (!userProfile.getLikedItems().contains(itemId)) {
                        candidateItems.add(itemId);
                        itemScores.put(itemId, 1.0);
                    }
                }

                log.info("地理位置召回完成，候选物品数量: {}", candidateItems.size());
                return RecallResult.builder()
                        .candidateItems(candidateItems)
                        .itemScores(itemScores)
                        .build();

            } catch (Exception e) {
                log.error("地理位置召回异常", e);
                return new RecallResult(new HashSet<>(), new HashMap<>());
            }
        }

        private List<String> getNearbyItems(String location) {
            String key = "location_items:" + location;
            @SuppressWarnings("unchecked")
            List<String> items = (List<String>) redisTemplate.opsForValue().get(key);
            return items != null ? items : new ArrayList<>();
        }
    }

    /**
     * 基于标签的召回
     */
    @Service
    public static class TagBasedRecall {

        @Autowired
        private RedisTemplate<String, Object> redisTemplate;

        public RecallResult recall(UserProfile userProfile, RecommendContext context) {
            try {
                log.info("开始基于标签的召回");

                Set<String> candidateItems = new HashSet<>();
                Map<String, Double> itemScores = new HashMap<>();

                // 1. 获取用户历史标签偏好
                Map<String, Double> tagPreferences = userProfile.getTagPreferences();

                // 2. 基于标签偏好召回
                for (Map.Entry<String, Double> entry : tagPreferences.entrySet()) {
                    String tag = entry.getKey();
                    Double preference = entry.getValue();

                    List<String> tagItems = getItemsByTag(tag);
                    for (String itemId : tagItems) {
                        if (!userProfile.getLikedItems().contains(itemId)) {
                            candidateItems.add(itemId);
                            itemScores.merge(itemId, preference, Double::sum);
                        }
                    }
                }

                log.info("基于标签的召回完成，候选物品数量: {}", candidateItems.size());
                return RecallResult.builder()
                        .candidateItems(candidateItems)
                        .itemScores(itemScores)
                        .build();

            } catch (Exception e) {
                log.error("基于标签的召回异常", e);
                return new RecallResult(new HashSet<>(), new HashMap<>());
            }
        }

        private List<String> getItemsByTag(String tag) {
            String key = "tag_items:" + tag;
            @SuppressWarnings("unchecked")
            List<String> items = (List<String>) redisTemplate.opsForValue().get(key);
            return items != null ? items : new ArrayList<>();
        }
    }
}

